#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
from gevent.pywsgi import WSGIServer, format_date_time
from gevent.lock import BoundedSemaphore
import time
import mimetypes
import posixpath
import uuid
import sys
import ConfigParser
import sqlite3
from cgi import parse,parse_qs
import json

server_version = "iws/1.0"
db_name = "info.db"
db_name1 = "links.db"

def guess_type(path):
    base, ext = posixpath.splitext(path)
    if ext in extensions_map:
        return extensions_map[ext]
    ext = ext.lower()
    if ext in extensions_map:
        return extensions_map[ext]
    else:
        return extensions_map['']  
        
def get_mtime(p):
    return format_date_time(os.path.getmtime(p))

def get_size(p):
    return os.path.getsize(p)    
    
reasons={
            "200":"OK",
            "404":"Not found",
            "400":"Bad request",
            "403": "Not allowed",
            "301":"Redirect",
            "206":"Partial Content",
            "416":"Range Not Satisfiable"
        }
        
        
def get_response_reason(code):
    return reasons[str(code)]

error_headers=[
                ("Server", server_version),
                ("Connection","close"),
                ("Content-Length","0")
                ]

class PcsSrv:
    def __init__(self):
        self.conn = sqlite3.connect(db_name)
        self.cur=self.conn.cursor()
        self.sem = BoundedSemaphore(1)
                
        self.conn1 = sqlite3.connect(db_name1)
        self.cur1 = self.conn1.cursor()

    def get_info(self, current_name):
        self.cur.execute("select current_name, origin_name, content_type, content_length, last_modified, etag from info where current_name = '%s'" % current_name)
        res = self.cur.fetchall()
        #cur.close()
        #conn.close()
        return res

    def update_times(slef, current_name):
        #conn = sqlite3.connect(db_name)
        #cur = conn.cursor()
        try:
            self.sem.acquire()
            self.cur.execute("update info set head_times = head_times + 1 where current_name = '%s'" % current_name)
            self.conn.commit()
        finally:
            self.sem.release()
        #cur.close()
        #conn.close()

    def get_headers(self, env):
        path=env["PATH_INFO"]
        p=os.path.normpath("%s%s" % (www_root, path))
        #info_p=os.path.normpath("%s/%s.txt" % (info_root,os.path.basename(p)))

        headers=[
            ("Server",server_version),
            ("Accept-Ranges","bytes"),
            ("Vary","Accept-Encoding"),
            #("Connection","close"),
        ]
        res = self.get_info(os.path.basename(path))
        if res:
            #with open(info_p) as fp:
            #    for l in fp:
            #        l = l.strip(" \r\n")
            #        k,v = l.split(":",1)
            #        headers.append((k,v))
            current_name, origin_name, content_type, content_length, last_modified, etag = res[0]
            headers.append(("Content-Type", str(content_type)))
            headers.append(("Content-Length", str(content_length)))
            headers.append(("Last-Modified", str(last_modified)))
            if etag:
                headers.append(("Etag", str(etag)))        
        elif os.path.exists(p) and os.path.isfile(p):
            mtime=get_mtime(p)
            size=get_size(p)
            ctype=guess_type(p)
            uid = uuid.uuid1()
            headers.append(("Last-Modified", mtime))
            headers.append(("Content-Length","%d" % size))
            headers.append(("Content-Type", ctype))
            headers.append(("ETag", '"%s"' % uid))
            #with open(info_p,"w") as fp:
            #    fp.write("Last-Modified:%s\r\n" % mtime)
            #    fp.write("Content-Length:%d\r\n" % size)
            #    fp.write("Content-Type:%s\r\n" % ctype)
            #    fp.write('ETag:"%s"\r\n' % uid)
        return headers

    def get_response_code(self, path, is_head=False):
        p=os.path.normpath("%s%s" % (www_root, path))
        #info_p=os.path.normpath("%s/%s.txt" % (info_root,os.path.basename(path)))
        res=self.get_info(os.path.basename(path))
        if is_head  and res:
            code = 200
            self.update_times(os.path.basename(path))
        elif not p.startswith(os.path.normpath(www_root)):
            code = 400
        elif os.path.exists(p) and os.path.isfile(p):
            code = 200
        elif os.path.isdir(p):
            code = 403
        else:
            code = 404
        return code

    def get_url(self, env, start_response):
        #"/url?num=10&start=2014-2-23 12:00:00&end=2012-3-10 12:00:00"
        #q=env["QUERY_STRING"]
        #q1=parse_qs(q)


        ltm = time.localtime(time.time() - 24 * 60 * 60)
        t1 = time.strftime("%Y-%m-%d %H:%M:%S", ltm)
        self.cur1.execute("select link, name from url where uptime > '%s'" % t1)
        res = self.cur1.fetchall()

        links = []
        for a in res:
            self.cur.execute("select origin_name from info where current_name = '%s'" % a[1])
            r1 = self.cur.fetchone()
            links.append((a[0],r1[0].decode("gbk")))

        #cur.close()
        #conn.close()
        #return res
        start_response("200 OK",[
            ("Content-Type", "application/json"),
            ("Server", server_version),
            ("Connection", "close")
            ])

        return json.dumps(links)
    
    def application(self, env, start_response):
        
        # request path
        path=env["PATH_INFO"]

        # path on filesystem
        p=os.path.normpath("%s%s" % (www_root, path))

        # request method
        method = env["REQUEST_METHOD"]

        # check request method
        if method not in ["GET", "HEAD"]:
            rcode = 400
            reason = get_response_reason(rcode)
            start_response("%d %s" % (rcode, reason), error_headers)
            return []
        
        if path == "/url":
            return self.get_url(env, start_response)
            
        # response code    
        if method == "HEAD":
            rcode = self.get_response_code(path, is_head=True)
        else:
            rcode = self.get_response_code(path, is_head=False)

        # reason
        reason = get_response_reason(rcode)

        # error
        if rcode != 200:
            start_response("%d %s" % (rcode, reason), error_headers)
            return []
        
        # get file infomation, such as modify time, type and size
        headers = self.get_headers(env)

        if method == "HEAD":
            headers.append(("Connection","close"))
        
        http_range=env.get("HTTP_RANGE")
        ranges = []
        if http_range:
            r=http_range.split("=")[1]
            rrs = r.split(",")
            for rg in rrs:
                ra = rg.split("-")
                if ra[0]:
                    start = int(ra[0])
                else:
                    start = 0
                if ra[1]:
                    end = int(ra[1])
                else:
                    end = get_size(p) - 1
                ranges.append((start,end))

        if len(ranges) > 1:
            rcode = 501
            reason = "Mutipart of range is not support"
            start_response("%d %s" % (rcode,reason), error_headers)
            return []

        if ranges:
            rcode =  206
            reason = get_response_reason(rcode)
            for i in range(len(headers)):
                if headers[i][0].lower() == "content-length":
                    headers.remove(headers[i])
                    break
            l = 0 
            lr = []
            for r in ranges:
                # check the valid of the range
                if r[0] > r[1] or r[1] >= get_size(p):
                    rcode = 416
                    reason = get_response_reason(rcode)
                    start_response("%d %s" % (rcode, reason), error_headers)
                    return []
                   
                # content length
                l += (r[1]-r[0]+1)
                lr.append("%d-%d/%d" % (r[0],r[1], get_size(p)))
                
            headers.append(("Content-Length",l))
            headers.append(("Content-Range","bytes %s" % ",".join(lr)))
            
        # send status code and headers
        write = start_response("%d %s" % (rcode, reason), headers)

        # send content to client
        if method == "GET":
            # range fetch
            if ranges:
                with open(p, "rb") as fp:
                    for start,end in ranges:
                        fp.seek(start)
                        remain = end - start + 1
                        while remain > 0:
                            block = 4096
                            if remain < 4096:
                                block = remain
                            data = fp.read(block)
                            write(data)
                            remain -= len(data)                  
            else: # whole file
                with open(p, "rb") as fp:
                    while True:
                        data = fp.read(4096)
                        if not data:
                            break
                        write(data)
        return []

if not mimetypes.inited:
    mimetypes.init() # try to read system mime.types
    extensions_map = mimetypes.types_map.copy()
    extensions_map.update({
        '': 'application/octet-stream', # Default
        '.py': 'text/plain',
        '.c': 'text/plain',
        '.h': 'text/plain',
        '.php': 'text/plain',
        })
        
application = PcsSrv().application

if __name__ == "__main__":

    www_root="/var/www/html"
    info_root="/tmp"

    configfile="server.cfg"

    for i in range(1, len(sys.argv)):
        if sys.argv[i] == "-c":
            configfile = sys.argv[i+1]
        if sys.argv[i] == "-h":
            print "Usage: %s -c config\n%s -h" % (sys.argv[0], sys.argv[0])
            sys.exit(0)

    cfg = ConfigParser.ConfigParser()
    cfg.read(configfile)

    if cfg.has_section("server"):
        if cfg.has_option("server","www_root"):
            www_root = cfg.get("server","www_root")
        if cfg.has_option("server","info_root"):
            info_root= cfg.get("server","info_root")
    port = cfg.getint("server","port")

    log=open("access.log","a", 0)

    WSGIServer(('',port),application, log=log).serve_forever()

